﻿// Inner Fire 游戏引擎库
// 渲染项目类-基类
//
// Copyright (c) 2024 by 尔西大帝. All rights reserved.
//
// 所有的RenderItem类都需要继承此类
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2024-12-21

#include "dx_text.h"
#include <graphics/imath.h>
#include <knumber.h>

namespace ifire::dx {
DxText::DxText(const DxTextDesc& desc)
    : UIRenderItem(desc.Widget, desc.Capacity)
    , length(desc.Length)
    , font(desc.Font)
    , font_size(desc.FontSize)
    , text_padding(desc.TextPadding)
    , text_align(desc.TextAlign) {
  // 初始化BUFFER
  buffer = new char[mCapacity];
  memset(buffer, 0, mCapacity);
  // 复制项目
  memcpy(buffer, desc.pText, length);

  // 文字的特殊计算：
  // 如果项目的大小为-1，则根据项目来计算宽高
  if (is_zero(mWidget.size.x) || is_zero(mWidget.size.y)) {
    // 因为这里得到的Size是NDC坐标，所以需要转化为屏幕坐标
    auto text_size = CalculatePixelSize(desc.ClientSize, mWidget.scale, font);
    if (is_zero(mWidget.size.x)) {
      mWidget.size.x = text_size.x;
    }
    if (is_zero(mWidget.size.y)) {
      mWidget.size.y = text_size.y;
    }
  }
}
DxText::~DxText() {
  // 析构函数删除项目
  delete[] buffer;
}

void DxText::SetPosition(DirectX::XMFLOAT2 position) {
  mWidget.position = position;
}

size_t DxText::Print(const char* format, ...) {
  va_list args;
  va_start(args, format);
  VPrint(format, args);
  va_end(args);
  // 返回写入的字符数（在VPrint中已经产生改变）
  return length;
}

size_t DxText::VPrint(const char* format, va_list args) {
  // 使用vsprintf将格式化的字符串存储到buffer中
  // length是写入的字符串
  length = vsnprintf(buffer, mCapacity, format, args);
  // 设置刷新
  SetRefresh();
  // 返回写入的字符数
  return length;
}

void DxText::SetText(const char* text, size_t len) {
  // 如果Len==0，则需要计算
  if (len == 0) {
    len = strlen(text);
  }

  // 检查上限
  if (len >= mCapacity) {
    len = mCapacity - 1;
  }

  // 复制字符串
  memcpy(buffer, text, len);
  // 设置结束符号
  buffer[len] = '\0';

  // 设置字符串长度
  length = len;

  // 设置刷新
  SetRefresh();
}

XMFLOAT2 DxText::CalculatePixelSize(
    XMFLOAT2 client_size, XMFLOAT2 scale, ui::BMFont* font) const {
  float aspect_rate = client_size.x / client_size.y;
  float padding = text_padding * scale.x;

  float total_width = 0.0f;
  float total_height = 0.0f;

  float height = font_size * scale.y;

  // 目前不支持多行文字，所以高度直接就等于文字高度
  total_height = height;

  // 计算每个文字的宽度
  for (size_t i = 0; i < length; i++) {
    auto& c_info = font->GetChar(buffer[i]);
    float scale_rate = c_info.uv_size.x / c_info.uv_size.y;
    float width = scale_rate * height / aspect_rate * scale.x;

    // 如果不是第一个，增加间隔
    total_width += i == 0 ? 0.0f : padding;
    // 增加宽度
    total_width += width;
  }

  return XMFLOAT2(total_width, total_height);
}

XMFLOAT2 DxText::SetChar(char c, XMFLOAT2 current_position,
    XMFLOAT2 client_size, XMFLOAT2 scale, UIInstancedData& data) const {
  // 从BMFont中查找项目
  auto& c_info = font->GetChar(c);

  // 屏幕比例，固定的
  float aspect_rate = client_size.x / client_size.y;
  // 文字间距
  float padding = text_padding / client_size.x * 2.0f * scale.x;

  // 高度和系统的比值
  // 这个乘2.0是因为NDC是从-1到1，也就是如果10像素针对1080，得到的是[0,1]，是一半。
  float height = font_size / client_size.y * scale.y;
  // 得到每个项目需要放大的大小
  auto padding_x = (font->mInfo.padding.left + font->mInfo.padding.right) /
                   font->mCommon.scaleW;
  auto padding_y = (font->mInfo.padding.top + font->mInfo.padding.bottom) /
                   font->mCommon.scaleH;
  float scale_rate =
      (c_info.uv_size.x + padding_x) / (c_info.uv_size.y + padding_y);
  float width = scale_rate * height / aspect_rate * scale.x;

  // 原来的屏幕比率，会导致高度变高，或者宽度变小，看怎么乘
  width /= aspect_rate;

  // CurrentPosition是文字左上角的位置。而文字的Mesh对应的是正中的位置，所以需要计算偏移
  XMFLOAT2 pos{};
  // 这里因为width是NDC坐标，所以一半即是自身，不用再乘0.5了。
  pos.x = current_position.x + width;
  pos.y = current_position.y - height;

  // 设置顶点的信息
  // TODO：这里是不正确的。等下修改。
  data.offset = pos;
  data.scale = XMFLOAT2(width, height);
  data.uv_offset = c_info.uv_point;
  data.uv_scale = c_info.uv_size;

  // 步进当前的位置（现在暂时不支持多行）
  // 这里之所以要乘2.0，因为width的值是以[0,1]区间对项目进行Scale的。是百分比值。
  // 但是NDC空间是[-1,1]。所以要进行一个2.0的乘法。
  current_position.x += (width * 2.0f) + padding;
  return current_position;
}

void DxText::Update(
    XMFLOAT2 client_size, UploadBuffer<UIInstancedData>* uploader) {
  // 用于更新的项目
  UIInstancedData data{};

  // 必须保证索引在Text的范围之内
  auto current_size = mPoolIndex + mCapacity;
  assert(current_size < uploader->size());

  // 如果项目不可见，直接全部设置为空
  if (mVisible == false) {
    data.SetEmpty();
    for (size_t i = 0; i < mCapacity; i++) {
      uploader->CopyData(mPoolIndex + i, data);
    }
    return;
  }

  // 计算得到文字的Widget
  auto widget = CalcWidget();

  // 计算整个项目的宽高
  auto text_size = CalculatePixelSize(client_size, widget.scale, font);

  // 根据文字的对齐方式，来决定第一个文字的起始位置
  XMFLOAT2 start_position = UIAlign::CalcTextStart(
      text_size, text_align, widget.position, widget.GetRealSize());

  // 清空位置
  XMFLOAT2 current_position_ = imath::ConvertToNDC(start_position, client_size);

  // 设置每个字符
  for (size_t i = 0; i < length; i++) {
    // 设置一个字符
    current_position_ =
        SetChar(buffer[i], current_position_, client_size, widget.scale, data);
    // 需要复制到cb_index指针的位置
    uploader->CopyData(mPoolIndex + i, data);
  }

  // 设置空字符
  if (mCapacity > length) {
    data.SetEmpty();
    for (size_t i = length; i < mCapacity; i++) {
      uploader->CopyData(mPoolIndex + i, data);
    }
  }
}

} // namespace ifire::dx